package edu.neu.cs6515.xml;

import lombok.Getter;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.bind.annotation.XmlAttribute;

import lombok.EqualsAndHashCode;
import lombok.ToString;

import com.google.java.contract.Invariant;

/**
 * Class represents XML Score information in the following format:
 * {@code
 *  <score orange=Nat red=Nat green=Nat yellow=Nat purple=Nat blue=Nat />
 * }
 */
@ToString
@EqualsAndHashCode
@Invariant({ ("getOrange() >= 0"), ("getRed() >= 0"), ("getGreen() >= 0"),
        ("getYellow() >= 0"), ("getPurple() >= 0"), ("getBlue() >= 0") })
public final class Score implements Cloneable, Comparable<Score> {
	@Getter
    private Map<IngeniousColor, Integer> scoreMap = new LinkedHashMap<IngeniousColor, Integer>();

    /**
     * Default Constructor, all colors set to 0;
     */
    public Score() {
        this(0, 0, 0, 0, 0, 0);
    }

    /**
     * C'tor for a players score
     * 
     * @param orange
     *            orange poins
     * @param red
     *            red points
     * @param green
     *            green points
     * @param yellow
     *            yellow points
     * @param purple
     *            purple points
     * @param blue
     *            blue points
     */
    public Score(final int orange, final int red, final int green,
            final int yellow, final int purple, final int blue) {
        scoreMap.put(IngeniousColor.ORANGE, orange);
        scoreMap.put(IngeniousColor.RED, red);
        scoreMap.put(IngeniousColor.GREEN, green);
        scoreMap.put(IngeniousColor.YELLOW, yellow);
        scoreMap.put(IngeniousColor.PURPLE, purple);
        scoreMap.put(IngeniousColor.BLUE, blue);
    }

    @XmlAttribute
    public int getOrange() {
        return scoreMap.get(IngeniousColor.ORANGE);
    }

    /**
     * Sets score value for orange
     * 
     * @param newOrangeScore
     *            new score for orange
     */
    public void setOrange(final int newOrangeScore) {
        scoreMap.put(IngeniousColor.ORANGE, newOrangeScore);
    }

    @XmlAttribute
    public int getRed() {
        return scoreMap.get(IngeniousColor.RED);
    }

    /**
     * Sets score value for red
     * 
     * @param newRedScore
     *            new score for red
     */
    public void setRed(final int newRedScore) {
        scoreMap.put(IngeniousColor.RED, newRedScore);
    }

    @XmlAttribute
    public int getGreen() {
        return scoreMap.get(IngeniousColor.GREEN);
    }

    /**
     * Sets score value for green
     * 
     * @param newGreenScore
     *            new score for green
     */
    public void setGreen(final int newGreenScore) {
        scoreMap.put(IngeniousColor.GREEN, newGreenScore);
    }

    @XmlAttribute
    public int getYellow() {
        return scoreMap.get(IngeniousColor.YELLOW);
    }

    /**
     * Sets score value for yellow
     * 
     * @param newYellowScore
     *            new score for yellow
     */
    public void setYellow(final int newYellowScore) {
        scoreMap.put(IngeniousColor.YELLOW, newYellowScore);
    }

    @XmlAttribute
    public int getPurple() {
        return scoreMap.get(IngeniousColor.PURPLE);
    }

    /**
     * Sets score value for purple
     * 
     * @param newPurpleScore
     *            new score for purple
     */
    public void setPurple(final int newPurpleScore) {
        scoreMap.put(IngeniousColor.PURPLE, newPurpleScore);
    }

    @XmlAttribute
    public int getBlue() {
        return scoreMap.get(IngeniousColor.BLUE);
    }

    /**
     * Sets score value for blue
     * 
     * @param newBlueScore
     *            new score for blue
     */
    public void setBlue(final int newBlueScore) {
        scoreMap.put(IngeniousColor.BLUE, newBlueScore);
    }

    /**
     * Adds the score of each color for each score in the given list
     * 
     * @param scores
     *            list of scores to be summed
     * @return new Score containing the sums for each color
     */
    public static Score sum(final List<Score> scores) {
        int totalOrange = 0;
        int totalRed = 0;
        int totalGreen = 0;
        int totalYellow = 0;
        int totalPurple = 0;
        int totalBlue = 0;
        for (final Score scoreForSide : scores) {
            totalOrange += scoreForSide.getOrange();
            totalRed += scoreForSide.getRed();
            totalGreen += scoreForSide.getGreen();
            totalYellow += scoreForSide.getYellow();
            totalPurple += scoreForSide.getPurple();
            totalBlue += scoreForSide.getBlue();
        }
        return new Score(totalOrange, totalRed, totalGreen, totalYellow,
                totalPurple, totalBlue);
    }

    /**
     * Adds one point to the sum of points for the given color
     * 
     * @param startColor
     *            color whose score is increased
     */
    public void addPointForColor(final IngeniousColor startColor) {
        int oldScore = scoreMap.get(startColor);
        scoreMap.put(startColor, ++oldScore);
    }

    /**
     * return a new Score with all fields equals to this one
     * 
     * @return the new Score
     */
    @Override
    public Object clone() {
        return new Score(getOrange(), getRed(), getGreen(), getYellow(),
                getPurple(), getBlue());
    }

    /**
     * compare the given score with this one by this lowest color
     * 
     * @param otherScore
     *            the other score object
     * @return 1 this this score is bigger than the given one 0 all color of
     *         this score is equal to the given one -1 the lowest color of this
     *         score is less then the given one
     */
    @Override
    public int compareTo(final Score otherScore) {
        if (this == otherScore) {
            return 0;
        }
        Iterator<Entry<IngeniousColor, Integer>> thisIterator = getIncreasingOrderSortedScore()
                .iterator();
        Iterator<Entry<IngeniousColor, Integer>> otherIterator = otherScore
                .getIncreasingOrderSortedScore().iterator();
        while (thisIterator.hasNext()) {
            Integer thisLowestColor = thisIterator.next().getValue();
            if (otherIterator.hasNext()) {
                Integer otherLowestColors = otherIterator.next().getValue();
                int compare = thisLowestColor.compareTo(otherLowestColors);
                if (compare != 0) {
                    return compare;
                }
            }
        }
        return 0;
    }

    /**
     * @return The score as a sorted treemap (sorted by score, increasing)
     */
    public List<Entry<IngeniousColor, Integer>> getIncreasingOrderSortedScore() {
        List<Entry<IngeniousColor, Integer>> list = new LinkedList<Entry<IngeniousColor, Integer>>(
                scoreMap.entrySet());

        Collections.sort(list,
                new Comparator<Entry<IngeniousColor, Integer>>() {
                    @Override
                    public int compare(
                            final Entry<IngeniousColor, Integer> arg0,
                            final Entry<IngeniousColor, Integer> arg1) {
                        return arg0.getValue().compareTo(arg1.getValue());
                    }
                });

        return list;
    }

    /**
     * get the score for given IngeniousColor
     * 
     * @param key
     *            the color to get the score
     * @return the score of the color
     */
    public int getPointsForColor(final IngeniousColor key) {
        return scoreMap.get(key);
    }

    /**
     * calculate the lowest scored colors
     * @return lowest scored color, if more than 1 color has the same score as the lowest
     * return all of them
     */
    public List<IngeniousColor> getLowestScoredColors() {
        List<Entry<IngeniousColor, Integer>> increasingScoredColor = this
                .getIncreasingOrderSortedScore();
        Entry<IngeniousColor, Integer> lowestEntry = increasingScoredColor
                .get(0);
        List<IngeniousColor> listOfLowestScoredColors = new LinkedList<IngeniousColor>();
        for (Entry<IngeniousColor, Integer> entry : increasingScoredColor) {
            if (lowestEntry.getValue().equals(entry.getValue())) {
                listOfLowestScoredColors.add(entry.getKey());
            }
        }
        return listOfLowestScoredColors;
    }

    /**
     * @return the summation of all colors
     */
    public int sumOfAllColors(){
        int sum = 0;
        for (int points : scoreMap.values()) {
            sum += points;
        }
        return sum;
    }
}
